using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Utility;

namespace SCSFContrib.CompositeUI.WPF.Activation
{
	/// <summary>
	/// An implementation of <see cref="IFrameworkElementActivationService"/> that monitors a <see cref="FrameworkElement"/> and activates and
	/// deactivates a <see cref="WorkItem"/> when the <see cref="FrameworkElement"/> gains or loses focus.
	/// </summary>
	/// <remarks>
	/// <para>
	/// The <c>FrameworkElementActivationService</c> is responsible for activating <see cref="WorkItem"/>s as the user navigates around a CAB
	/// application. Essentially, as <see cref="FrameworkElement"/>s receive keyboard focus, this service will ensure the correct
	/// <see cref="WorkItem"/> is activated.
	/// </para>
	/// <para>
	/// Sometimes it is necessary to ensure that focusing on certain elements does not cause the related <see cref="WorkItem"/> to be activated.
	/// For example, this is most often true menu items. The <c>IsExempt</c> attached property allows specific <see cref="FrameworkElement"/>s to
	/// receive focus without affecting the currently activated <see cref="WorkItem"/>.
	/// </para>
	/// <para>
	/// Note that, by default, <see cref="MenuItem"/>s are exempt (ie. <see cref="GetIsExempt"/> will return <see langword="true"/>). This is most
	/// often the desired behavior, but can be overridden by explicitly setting the <c>IsExempt</c> attached property to <see langword="false"/>.
	/// All other <see cref="FrameworkElement"/>s are not exempt by default.
	/// </para>
	/// </remarks>
	public sealed class FrameworkElementActivationService : IFrameworkElementActivationService
	{
		private readonly WorkItem _workItem;

		/// <summary>
		/// Identifies the <c>IsExempt</c> attached property.
		/// </summary>
		public static readonly DependencyProperty IsExemptProperty = DependencyProperty.RegisterAttached("IsExempt",
			typeof(bool),
			typeof(FrameworkElementActivationService));

		/// <summary>
		/// Constructs an instance of <c>FrameworkElementActivationService</c>.
		/// </summary>
		/// <param name="workItem">
		/// The <see cref="WorkItem"/> in which this service resides.
		/// </param>
		public FrameworkElementActivationService([ServiceDependency] WorkItem workItem)
		{
			Guard.ArgumentNotNull(workItem, "workItem");
			_workItem = workItem;
		}

		private delegate void FrameworkElementHandler(FrameworkElement frameworkElement);

		/// <summary>
		/// See <see cref="IFrameworkElementActivationService.FrameworkElementAdded"/>.
		/// </summary>
		public void FrameworkElementAdded(FrameworkElement frameworkElement)
		{
			Guard.ArgumentNotNull(frameworkElement, "frameworkElement");

			if (!frameworkElement.Dispatcher.CheckAccess())
			{
				frameworkElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send, new FrameworkElementHandler(FrameworkElementAdded), frameworkElement);
			}
			else
			{
				frameworkElement.Loaded += frameworkElement_Loaded;
				frameworkElement.Unloaded += frameworkElement_Unloaded;
			}
		}

		/// <summary>
		/// See <see cref="IFrameworkElementActivationService.FrameworkElementRemoved"/>.
		/// </summary>
		public void FrameworkElementRemoved(FrameworkElement frameworkElement)
		{
			Guard.ArgumentNotNull(frameworkElement, "frameworkElement");

			if (!frameworkElement.Dispatcher.CheckAccess())
			{
				frameworkElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send, new FrameworkElementHandler(FrameworkElementRemoved), frameworkElement);
			}
			else
			{
				frameworkElement.Loaded -= frameworkElement_Loaded;
				frameworkElement.Unloaded -= frameworkElement_Unloaded;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the specified <see cref="FrameworkElement"/> is exempt from special treatment by this
		/// <c>FrameworkElementActivationService</c>.
		/// </summary>
		/// <param name="frameworkElement">
		/// The <see cref="FrameworkElement"/> in question.
		/// </param>
		/// <returns>
		/// <see langword="true"/> if <paramref name="frameworkElement"/> is exempt from special treatment, otherwise <see langword="false"/>.
		/// </returns>
		public static bool GetIsExempt(FrameworkElement frameworkElement)
		{
			Guard.ArgumentNotNull(frameworkElement, "frameworkElement");

			//by default, clicking on menu items will not cause the related work item to be activated
			if ((frameworkElement is MenuItem) && (DependencyPropertyHelper.GetValueSource(frameworkElement, IsExemptProperty).BaseValueSource == BaseValueSource.Default))
			{
				return true;
			}

			return (bool)frameworkElement.GetValue(IsExemptProperty);
		}

		/// <summary>
		/// Sets a value indicating whether a given <see cref="FrameworkElement"/> should be ignored by this
		/// <c>FrameworkElementActivationService</c>.
		/// </summary>
		/// <param name="frameworkElement">
		/// The <see cref="FrameworkElement"/> in question.
		/// </param>
		/// <param name="value">
		/// <see langword="true"/> to ignore focus events raised by <paramref name="frameworkElement"/>.
		/// </param>
		public static void SetIsExempt(FrameworkElement frameworkElement, bool value)
		{
			Guard.ArgumentNotNull(frameworkElement, "frameworkElement");
			frameworkElement.SetValue(IsExemptProperty, value);
		}

		private void frameworkElement_Loaded(object sender, RoutedEventArgs e)
		{
			Debug.Assert(sender is FrameworkElement);
			(sender as FrameworkElement).GotKeyboardFocus += frameworkElement_GotKeyboardFocus;
		}

		private void frameworkElement_Unloaded(object sender, RoutedEventArgs e)
		{
			Debug.Assert(sender is FrameworkElement);
			(sender as FrameworkElement).GotKeyboardFocus -= frameworkElement_GotKeyboardFocus;
		}

		private void frameworkElement_GotKeyboardFocus(object sender, RoutedEventArgs e)
		{
			Debug.Assert(sender is FrameworkElement);
			e.Handled = ActivateWorkItemForFrameworkElement(sender as FrameworkElement);
		}

		private bool ActivateWorkItemForFrameworkElement(FrameworkElement frameworkElement)
		{
			Debug.Assert(frameworkElement != null);

			if (GetIsExempt(frameworkElement))
			{
				return false;
			}

			_workItem.Activate();
			return true;
		}
	}
}